
#ifndef INT64_C 
#define INT64_C(c) (c ## LL) 
#define UINT64_C(c) (c ## ULL) 
#endif

extern "C" {
	/* Include ffmpeg header file */
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>

#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
}

#include <opencv2/opencv.hpp>
#include <iostream>


/**
* FFMPEG 基本概念说明：
* 1. 首先，定义输入输出 AVFormatContext 结构体，这类结构体存储音视频数据，也就是音视频文件的一种抽象和封装。
* 2. 然后，定义输入输出文件名。例如：输入为 rtsp 协议的地址，输出为一个 flv 文件。（FLV 是 FLASH VIDEO 的简称，由于它形成的文件极小、加载速度极快，使得网络观看视频文件成为可能。）
* 3. 注意，网络视频可能需要初始化网络配置，调用 avformat_network_init。
* 4. 设置一个配置字典 AVDictionary 结构体，用于输入流的打开方式配置。
* 5. 打开输入源： int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
*    - 一个指向 AVFormatContext 指针的指针。
*    - 一个指向 url 的指针。
*    - AVInputFormat 用于指定输入的封装格式。一般传 NULL，由 FFmpeg 自行探测！！
*    - AVDictionary **options 是一个字典，用于参数传递。
* 6. 得到流和码率等信息： avformat_find_stream_info(ifmt_ctx, 0)；
* 7. 获取解码器上下文：
*    - AVCodec *codec =  avcodec_find_decoder(stream->codecpar->codec_id);
*    - AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
*    - avcodec_parameters_to_context(codec_ctx, stream->codecpar);
*    - av_codec_set_pkt_timebase(codec_ctx, stream->time_base);
*    - avcodec_open2(codec_ctx, codec, nullptr);
* 8. 定义 AVPacket pkt;
*   （AVPacket 类保存解复用后，解码之前的数据。至于什么是解复用，我们都知道信号有时分复用，频分复用等，音视频信号中经常将视频音频等进行复用，
*    在接收端就要把它们独立分离出来，即 Source->Demux->Stream 的变化。）
*    - AVPacket 是 FFmpeg 中很重要的一个数据结构，它保存了解复用（demuxer)之后，解码（decode）之前的数据（仍然是压缩后的数据）和关于这些数据的一些附加的信息，
*      如：显示时间戳（pts），解码时间戳（dts）,数据时长（duration），所在流媒体的索引（stream_index）等等。
*    - 对于视频（Video）来说，AVPacket 通常包含一个压缩的 Frame；而音频（Audio）则有可能包含多个压缩的 Frame。
*    - 一个 packet 也有可能是空的，不包含任何压缩数据 data，只含有边缘数据 side data。（side data，容器提供的关于packet的一些附加信息，如：在编码结束的时候更新一些流的参数。）
* 9. 解码
* 10. swscale 变换
*/



int main(int argc, char** argv)
{
	/**
	* tutorials:
	* - http://dranger.com/ffmpeg/tutorial01.html
	* - https://blog.csdn.net/luotuo44/article/details/54981809
	* - https://blog.csdn.net/qq_33316004/article/details/100113963
	* - https://blog.csdn.net/u013241583/article/details/104796741/ 'Could not find ref with POC'
	*/
	/**
	* Initialize libavformat and register all the muxers, demuxers and
	* protocols. If you do not call this function, then you can select
	* exactly which formats you want to support.
	*
	* @see av_register_input_format()
	* @see av_register_output_format()
	*/
	//av_register_all();

	/**
	* Do global initialization of network libraries. This is optional,
	* and not recommended anymore.
	*
	* This functions only exists to work around thread-safety issues
	* with older GnuTLS or OpenSSL libraries. If libavformat is linked
	* to newer versions of those libraries, or if you do not use them,
	* calling this function is unnecessary. Otherwise, you need to call
	* this function before any other threads using them are started.
	*
	* This function will be deprecated once support for older GnuTLS and
	* OpenSSL libraries is removed, and this function has no purpose
	* anymore.
	*/
	//avformat_network_init();

	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	const char *in_filename, *out_filename;

	in_filename = "rtmp://58.200.131.2:1935/livetv/hunantv";
	out_filename = "output.flv";


	/**
	* Open an input stream and read the header. The codecs are not opened.
	* The stream must be closed with avformat_close_input().
	*
	* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
	*           May be a pointer to NULL, in which case an AVFormatContext is allocated by this
	*           function and written into ps.
	*           Note that a user-supplied AVFormatContext will be freed on failure.
	* @param url URL of the stream to open.
	* @param fmt If non-NULL, this parameter forces a specific input format.
	*            Otherwise the format is autodetected.
	* @param options  A dictionary filled with AVFormatContext and demuxer-private options.
	*                 On return this parameter will be destroyed and replaced with a dict containing
	*                 options that were not found. May be NULL.
	*
	* @return 0 on success, a negative AVERROR on failure.
	*
	* @note If you want to use custom IO, preallocate the format context and set its pb field.
	*/

	AVDictionary* avdic = NULL;
	char option_key[] = "rtsp_transport";
	char option_value[] = "tcp";
	av_dict_set(&avdic, option_key, option_value, 0);
	//char option_key2[] = "max_delay";
	//char option_value2[] = "5000000";


	int ret = -1;
	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, &avdic)) < 0) {
		std::cerr << "Couldn't open input file." << std::endl;
		goto end;
	}


	/**
	* Read packets of a media file to get stream information. This
	* is useful for file formats with no headers such as MPEG. This
	* function also computes the real framerate in case of MPEG-2 repeat
	* frame mode.
	* The logical file position is not changed by this function;
	* examined packets may be buffered for later processing.
	*
	* @param ic media file handle
	* @param options  If non-NULL, an ic.nb_streams long array of pointers to
	*                 dictionaries, where i-th member contains options for
	*                 codec corresponding to i-th stream.
	*                 On return each dictionary will be filled with options that were not found.
	* @return >=0 if OK, AVERROR_xxx on error
	*
	* @note this function isn't guaranteed to open all the codecs, so
	*       options being non-empty at return is a perfectly normal behavior.
	*
	* @todo Let the user decide somehow what information is needed so that
	*       we do not waste time getting stuff the user does not need.
	*/

	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
		std::cerr << "Couldn't find stream information." << std::endl;
		goto end;
	}

	/**
	* Print detailed information about the input or output format, such as
	* duration, bitrate, streams, container, programs, metadata, side data,
	* codec and time base.
	*
	* @param ic        the context to analyze
	* @param index     index of the stream to dump information about
	* @param url       the URL to print, such as source or destination file
	* @param is_output Select whether the specified context is an input(0) or output(1)
	*/
	av_dump_format(ifmt_ctx, 0, in_filename, 0);


	/**
	* Now, let's walk through it until we find a video stream
	* @Note: codec is deprecated, use the codecpar struct instead
	*/

	int video_index = -1;
	for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
		if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			video_index = i;
			std::cout << "Stream " << i << " get videostream." << std::endl;
			break;
		}
	}

	/**
	* Get a pointer to the codec context for the video stream.
	* The stream's information about the codec is in what we call the "codec context."
	* Now we have a pointer to it. But we still have to find the actual codec and open it.
	*/

	AVCodecParameters *codec_par = ifmt_ctx->streams[video_index]->codecpar;

	AVCodecContext * codec_ctx = NULL;
	AVCodec *codec;

	codec = avcodec_find_decoder(codec_par->codec_id);
	//video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);

	if (codec == NULL) {
		std::cerr << "Unsupported codec!" << std::endl;
		goto end;
	}

	codec_ctx = avcodec_alloc_context3(codec);
	if (codec_ctx == NULL) {
		std::cerr << "Allocate AVCodecContext Failed!" << std::endl;
		goto end;
	}
	if (avcodec_parameters_to_context(codec_ctx, codec_par) < 0) {
		std::cerr << "Fill the codec context failed." << std::endl;
		goto end;
	}
	if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
		std::cerr << "Could not open codec." << std::endl;
		goto end;
	}

	/**
	* Note that we must not use the AVCodecContext from the video stream directly!!!
	*/

	/**
	* Allocate an AVFormatContext for an output format.
	* avformat_free_context() can be used to free the context and
	* everything allocated by the framework within it.
	*
	* @param *ctx is set to the created format context, or to NULL in
	* case of failure
	* @param oformat format to use for allocating the context, if NULL
	* format_name and filename are used instead
	* @param format_name the name of output format to use for allocating the
	* context, if NULL filename is used instead
	* @param filename the name of the filename to use for allocating the
	* context, may be NULL
	* @return >= 0 in case of success, a negative AVERROR code in case of
	* failure
	*/
	//avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
	//if (!ofmt_ctx) {
	//	std::cerr << "Could not create output AVFormatContext\n" << std::endl;
	//	goto end;
	//}

	//AVStream *stream = avformat_new_stream(ofmt_ctx, nullptr);
	//AVCodec *ofcodec = avcodec_find_encoder(ofmt_ctx->oformat->video_codec);
	//AVCodecContext *ofcodec_ctx = avcodec_alloc_context3(ofcodec);

	////ofcodec_ctx->video_type = AVMEDIA_TYPE_VIDEO;
	////ofcodec_ctx->format = AV_PIX_FMT_YUV420P;
	//ofcodec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
	//ofcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	//ofcodec_ctx->codec_id = ofmt_ctx->oformat->video_codec;
	//ofcodec_ctx->width = 1280;
	//ofcodec_ctx->height = 720;
	//ofcodec_ctx->gop_size = 12;
	//ofcodec_ctx->time_base = AVRational{ 1, 25 };
	//ofcodec_ctx->bit_rate = 1400 * 1000;

	//avcodec_open2(ofcodec_ctx, ofcodec, nullptr);
	//avcodec_parameters_from_context(stream->codecpar, ofcodec_ctx);
	////av_stream_set_r_frame_rate(stream, { 1, 25 });


	//for (int i = 0; i < ifmt_ctx->nb_streams; i++) {

	//	AVStream* in_stream = ifmt_ctx->streams[i];
	//	AVStream* out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);

	//	if (!out_stream) {
	//		std::cerr << "Failed allocating output stream" << i << "." << std::endl;
	//		ret = AVERROR_UNKNOWN;
	//		goto end;
	//	}

	//	ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
	//	if (ret < 0) {
	//		std::cerr << "Failed to copy context from input to output stream codec context." << std::endl;
	//		goto end;
	//	}

	//	out_stream->codec->codec_tag = 0;

	//	if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
	//		out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
	//	}
	//}


	//avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);  //打开文件！
	//ret = avformat_write_header(ofmt_ctx, NULL);
	//if (ret < 0)
	//{
	//	std::cerr << "Error occured when opening output URL\n" << std::endl;
	//	goto end;
	//}



	/**
	* ========================= Now we need a place to actually store the frame =========================
	* Since we're planning to output files, which are stored in 24-bit RGB, we're going to have to convert our frame from its native format to RGB.
	* For most projects (including ours) we're going to want to convert our initial frame to a specific format. Let's allocate a frame for the converted frame now.
	*/

	int unfinished;
	AVPacket pkt;
	AVFrame *frame = NULL, *swframe = NULL;
	frame = av_frame_alloc();
	swframe = av_frame_alloc();

	/**
	* Even though we've allocated the frame, we still need a place to put the raw data when we convert it.
	*
	* av_malloc is ffmpeg's malloc that is just a simple wrapper around malloc that makes sure the memory addresses are aligned and such.
	* It will not protect you from memory leaks, double freeing, or other malloc problems.
	*/
	uint8_t *buffer = NULL;
	int numBytes;
	//numBytes = avpicture_get_size(AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height);
	numBytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, 1);
	buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));

	/**
	* Now we use avpicture_fill to associate the frame with our newly allocated buffer. (deprecated!!!!)
	* About the AVPicture cast: the AVPicture struct is a subset of the AVFrame struct - the beginning of the AVFrame struct is identical to the AVPicture struct.
	* NOTE: AVFrame is a superset of AVPicture.
	*/
	//avpicture_fill((AVPicture *)swframe, buffer, AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height);
	av_image_fill_arrays(swframe->data, swframe->linesize, buffer, AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, 1);


	//AVPicture picture{};
	//avpicture_alloc(&picture, AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height);
	struct SwsContext *img_cvrt_ctx = nullptr;



	int keyval = 0;
	while (true) {

		AVStream* in_stream, *out_stream;

		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0) {
			std::cerr << "[Error]: av_read_frame failed." << std::endl;
			break;
		}

		in_stream = ifmt_ctx->streams[pkt.stream_index];
		//out_stream = ofmt_ctx->streams[pkt.stream_index];

		if (pkt.stream_index == video_index) {

			/**********************************************************************************
			*                                     New API                                     *
			***********************************************************************************/

			/**
			* avcodec_send_packet 发送数据到 ffmepg，放到解码队列中。
			* avcodec_receive_frame 将成功的解码队列中取出 1 个 frame（如果失败会返回０）
			*/

			/**
			* Supply raw packet data as input to a decoder.
			*
			* Internally, this call will copy relevant AVCodecContext fields, which can
			* influence decoding per-packet, and apply them when the packet is actually
			* decoded. (For example AVCodecContext.skip_frame, which might direct the
			* decoder to drop the frame contained by the packet sent with this function.)
			*
			* @warning The input buffer, avpkt->data must be AV_INPUT_BUFFER_PADDING_SIZE
			*          larger than the actual read bytes because some optimized bitstream
			*          readers read 32 or 64 bits at once and could read over the end.
			*
			* @warning Do not mix this API with the legacy API (like avcodec_decode_video2())
			*          on the same AVCodecContext. It will return unexpected results now
			*          or in future libavcodec versions.
			*
			* @note The AVCodecContext MUST have been opened with @ref avcodec_open2()
			*       before packets may be fed to the decoder.
			*/
			ret = avcodec_send_packet(codec_ctx, &pkt);
			if (ret < 0) {
				std::cerr << "avcodec_send_packet encounter error." << std::endl;
				goto end;
			}

			while (ret >= 0) {
				ret = avcodec_receive_frame(codec_ctx, frame);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
					break;
				}
				else if (ret < 0) {
					goto end;  //end处进行资源释放等善后处理
				}
				if (ret >= 0) {
					img_cvrt_ctx = sws_getCachedContext(img_cvrt_ctx, codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
						codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);
					sws_scale(img_cvrt_ctx, reinterpret_cast<const uint8_t *const *>(frame->data), frame->linesize, 0, codec_ctx->height, swframe->data, swframe->linesize);

					cv::Mat image(codec_ctx->height, codec_ctx->width, CV_8UC3, swframe->data[0]);
					//cv::Mat image(codec_ctx->height, codec_ctx->width, CV_8UC3, cv::Scalar(0, 255, 255));
					cv::imshow("demo", image);
					keyval = cv::waitKey(1);
				}
			}

			if (27 == keyval) {
				av_packet_unref(&pkt);
				break;
			}


			/**********************************************************************************
			*                                     Old API                                     *
			***********************************************************************************/

			// Decode the video frame: packet => frame
			//avcodec_decode_video2(codec_ctx, frame, &unfinished, &pkt);

			//if (unfinished) {
			//	// Convert to BGR24.
			//	img_cvrt_ctx = sws_getCachedContext(
			//		img_cvrt_ctx,
			//		codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
			//		codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24,
			//		SWS_BICUBIC, nullptr, nullptr, nullptr);
			//	sws_scale(img_cvrt_ctx,
			//		reinterpret_cast<const uint8_t *const *>(frame->data), frame->linesize,
			//		0, codec_ctx->height,
			//		picture.data, picture.linesize);

			//	// Use OpenCV to display the picture.
			//	cv::Mat img(frame->height, frame->width, CV_8UC3, picture.data[0]);
			//	cv::imshow("demo", img);
			//	keyval = cv::waitKey(1);
			//}

			//if (27 == keyval) {
			//	av_packet_unref(&pkt);
			//	break;
			//}

			/**********************************************************************************
			*                                                                                 *
			***********************************************************************************/
		}

		////copy packet
		////转换 PTS/DTS 时序
		//pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		//pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		////printf("pts %d dts %d base %d\n",pkt.pts,pkt.dts, in_stream->time_base);
		//pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		//pkt.pos = -1;


		//ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
		//if (ret < 0)
		//{
		//	if (ret == -22) {
		//		continue;
		//	}
		//	else {
		//		printf("Error muxing packet.error code %d\n", ret);
		//		break;
		//	}
		//}

		av_packet_unref(&pkt);
	}

	//av_write_trailer(ofmt_ctx);


end:
	//sws_freeContext(img_cvrt_ctx);
	av_frame_free(&frame);
	av_frame_free(&swframe);
	avcodec_free_context(&codec_ctx);
	av_dict_free(&avdic);
	avformat_close_input(&ifmt_ctx);
	//if (ofmt_ctx && !(ofmt_ctx->flags & AVFMT_NOFILE)) avio_close(ofmt_ctx->pb);
	//avformat_free_context(ofmt_ctx);
	//avformat_free_context(ofmt_ctx);
	std::cout << "end." << std::endl;
	return 0;
}
